package com.autonavi.yunda.yunji.controller;

import com.autonavi.yunda.yunji.common.enums.EditType;
import com.autonavi.yunda.yunji.common.exception.AmapPreconditions;
import com.autonavi.yunda.yunji.common.utils.JsonUtils;
import com.autonavi.yunda.yunji.common.utils.http.HttpUtil;
import com.autonavi.yunda.yunji.common.web.entity.GlobalResponse;
import com.autonavi.yunda.yunji.controller.request.EngineDebugAllRequest;
import com.autonavi.yunda.yunji.core.config.EngineConstant;
import com.autonavi.yunda.yunji.core.engine.argresolver.ParamResolver;
import com.autonavi.yunda.yunji.core.enums.InterfaceStatus;
import com.autonavi.yunda.yunji.core.enums.OperationRecordType;
import com.autonavi.yunda.yunji.core.enums.ParamSourceType;
import com.autonavi.yunda.yunji.core.model.dto.InterfaceConfigDTO;
import com.autonavi.yunda.yunji.core.model.dto.TaskConfigDTO;
import com.autonavi.yunda.yunji.core.model.vo.InterfaceData;
import com.autonavi.yunda.yunji.core.service.DebugService;
import com.autonavi.yunda.yunji.core.service.InterfaceConfigService;
import com.autonavi.yunda.yunji.core.service.TaskConfigService;
import com.autonavi.yunda.yunji.core.utils.ExportUtil;
import com.autonavi.yunda.yunji.core.vo.DebugResult;
import com.autonavi.yunda.yunji.core.vo.EngineData;
import com.autonavi.yunda.yunji.core.vo.node.BaseNode;
import com.autonavi.yunda.yunji.core.vo.node.StartNode;
import com.autonavi.yunda.yunji.core.vo.param.Param;
import com.autonavi.yunda.yunji.core.vo.response.SingleNodeDebugResultVO;
import com.autonavi.yunda.yunji.domain.DebugSingleNodeVO;
import com.autonavi.yunda.yunji.service.OperationRecordService;
import com.autonavi.yunda.yunji.service.ProjectAuthService;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author cizhong.wcs
 * @date 2022/6/14 17:37
 */
@RestController
@RequestMapping("/debug")
public class DebugController {
    @Resource
    DebugService debugService;
    @Resource
    InterfaceConfigService interfaceConfigService;
    @Value("${yunji.engine.host}")
    String engineHost;
    @Resource
    OperationRecordService operationRecordService;
    @Resource
    ProjectAuthService projectAuthService;
    @Resource
    TaskConfigService taskConfigService;

    /**
     * 普通节点调试
     * @param request
     * @return
     */
    @PostMapping("/singleNode")
    public Object singleNode(@RequestBody JsonNode request) {
        return debugSingleNode(new DebugSingleNodeVO(request));
    }

    @PostMapping(path = "/formSingleNode")
    public Object formSingleNode(MultipartHttpServletRequest request) {
        return debugSingleNode(new DebugSingleNodeVO(request));
    }

    /**
     * function节点调试
     * @param request
     * @return
     */
    @PostMapping("/singleNodeWithDetail")
    public SingleNodeDebugResultVO singleNodeWithDetail(@RequestBody JsonNode request) {
        return debugSingleNodeWithDetail(new DebugSingleNodeVO(request));
    }

    @PostMapping(path = "/formSingleNodeWithDetail")
    public Object formSingleNodeWithDetail(MultipartHttpServletRequest request) {
        return debugSingleNodeWithDetail(new DebugSingleNodeVO(request));
    }

    /**
     * 全节点调试
     */
    @PostMapping("/allNode")
    public DebugResult engineDebugAllNode(@RequestBody @Validated EngineDebugAllRequest request, HttpServletResponse response) {
        projectAuthService.checkProjectPermission(request.getId(), request.getEditType());
        DebugResult debugResult = debugService.debugAllNode(request.getId(), request.getEditType(), request.toHttpRequest(null));
        updateInterfaceStatusAndResponse(request, debugResult);
        boolean exported = exportResultFileByExportType(debugResult, response, request.getExportType());
        if (EditType.TASK.name().equalsIgnoreCase(request.getEditType())) {
            operationRecordService.add(OperationRecordType.TaskSubType.DEBUG_ALL_NODE, request.getId().toString());
        } else {
            operationRecordService.add(OperationRecordType.InterfaceSubType.DEBUG_ALL_NODE, request.getId().toString());
        }
        return exported ? null : debugResult;
    }

    @PostMapping("/formAllNode")
    public DebugResult engineDebugAllNode(MultipartHttpServletRequest multipartRequest, HttpServletResponse response) {
        AmapPreconditions.checkArgumentForUser(!multipartRequest.getParameterMap().isEmpty() && multipartRequest.getParameterMap().get("jsonNode") != null, "参数不能为空");
        EngineDebugAllRequest debugAllRequest = JsonUtils.from(multipartRequest.getParameterMap().get("jsonNode")[0], EngineDebugAllRequest.class);
        projectAuthService.checkProjectPermission(debugAllRequest.getId(), debugAllRequest.getEditType());
        checkEngineDebugAllRequest(debugAllRequest);
        DebugResult debugResult = debugService.debugAllNode(debugAllRequest.getId(), debugAllRequest.getEditType(), debugAllRequest.toHttpRequest(multipartRequest.getMultiFileMap()));
        updateInterfaceStatusAndResponse(debugAllRequest, debugResult);
        boolean exported = exportResultFileByExportType(debugResult, response, debugAllRequest.getExportType());
        if (EditType.TASK.name().equalsIgnoreCase(debugAllRequest.getEditType())) {
            operationRecordService.add(OperationRecordType.TaskSubType.DEBUG_ALL_NODE, debugAllRequest.getId().toString());
        } else {
            operationRecordService.add(OperationRecordType.InterfaceSubType.DEBUG_ALL_NODE, debugAllRequest.getId().toString());
        }
        return exported ? null : debugResult;
    }

    /**
     * 拼接最终的sql和执行行数，并返回至前端进行查看确认
     *
     * @param request request
     * @return sql
     */
    @PostMapping("/showFinalSqlNRowNum")
    public Object showFinalSql(@RequestBody JsonNode request) {
        BaseNode baseNode = BaseNode.parseNode(request);
        Long interfaceId = request.get("interfaceId").asLong();
        JsonNode editType = request.get("editType");
        String projectId;
        if (editType == null || editType.asText().equalsIgnoreCase(EditType.INTERFACE.name())) {
            projectId = interfaceConfigService.findByIdOrThrow(interfaceId).getProjectId();
        } else {
            projectId = taskConfigService.findByIdOrThrow(interfaceId).getProjectId();
        }
        baseNode = DebugSingleNodeVO.decodeNode(baseNode);
        return debugService.debugNode(baseNode, projectId);
    }

    @GetMapping("/generateCurl")
    public String generateCurl(@RequestParam Long id) {
        InterfaceConfigDTO dto = interfaceConfigService.findByIdOrThrow(id);
        String url = engineHost + dto.getUri();
        if (Strings.isBlank(dto.getEngineData())) {
            return HttpUtil.generateCurl(url, Map.of(), dto.getMethod().name(), Map.of(), Map.of(), null, Map.of());
        }
        EngineData engineData = new EngineData(dto.getEngineData());
        StartNode startNode = engineData.fetchStartNode();
        Map<String, Object> paramMap = startNode.inputParams.stream()
                .filter(p -> ParamSourceType.query.equals(p.getSource()))
                .collect(Collectors.toMap(Param::getName, p -> StringUtils.isBlank(p.getDebugValue()) ? "" : p.getDebugValue()));
        Map<String, String> headers = startNode.inputParams.stream()
                .filter(p -> ParamSourceType.headers.equals(p.getSource()))
                //headers中key统一改为小写
                .collect(Collectors.toMap(p -> p.getName().toLowerCase(), p -> StringUtils.isBlank(p.getDebugValue()) ? "" : p.getDebugValue()));
        Map<String, Object> bodyMap = new HashMap<>(8);
        startNode.inputParams.stream()
                .filter(p -> ParamSourceType.body.equals(p.getSource()))
                .forEach(p -> bodyMap.put(p.name, ParamResolver.getParamValueByType(StringUtils.isBlank(p.getDebugValue()) ? "" : p.getDebugValue(), p.type)));
        Map<String, String> fileMarkMap = startNode.inputParams.stream()
                .filter(p -> ParamSourceType.body.equals(p.getSource()))
                .collect(Collectors.toMap(p -> p.name, p -> p.type));
        return HttpUtil.generateCurl(url, paramMap, dto.getMethod().name(), bodyMap, headers, startNode.contentType, fileMarkMap);
    }

    private void checkEngineDebugAllRequest(EngineDebugAllRequest engineDebugAllRequest) {
        AmapPreconditions.checkArgumentForUser(engineDebugAllRequest.getId() != null, "id不能为空");
        Optional<Param> first = engineDebugAllRequest.getInputParams().stream().filter(param -> StringUtils.isBlank(param.type) || StringUtils.isBlank(param.name)).findFirst();
        AmapPreconditions.checkArgumentForUser(first.isEmpty(), "name或type不能为空");
    }

    private void updateInterfaceStatusAndResponse(EngineDebugAllRequest debugAllRequest, DebugResult debugResult) {
        if (EngineConstant.DEBUG_EXEC_SUCCESS.equals(debugResult.executeStatus)) {
            if (EditType.TASK.name().equalsIgnoreCase(debugAllRequest.getEditType())) {
                TaskConfigDTO dto = taskConfigService.findByIdOrThrow(debugAllRequest.getId());
                //灰度和发布中的调试成功不更新状态
                if (!InterfaceStatus.GRAY.equals(dto.getStatus()) && !InterfaceStatus.PUBLISHED.equals(dto.getStatus())) {
                    taskConfigService.updateStatus(debugAllRequest.getId(), InterfaceStatus.TEST_SUCCESS);
                }
            } else {
                InterfaceConfigDTO dto = interfaceConfigService.findByIdOrThrow(debugAllRequest.getId());
                //灰度和发布中的调试成功不更新状态
                if (!InterfaceStatus.GRAY.equals(dto.getStatus()) && !InterfaceStatus.PUBLISHED.equals(dto.getStatus())) {
                    interfaceConfigService.updateStatus(debugAllRequest.getId(), InterfaceStatus.TEST_SUCCESS);
                }
                InterfaceData interfaceData = dto.getInterfaceData();
                interfaceData.response = debugResult.response;
                interfaceConfigService.updateInterfaceData(debugAllRequest.getId(), interfaceData);
            }
        }
    }

    private SingleNodeDebugResultVO debugSingleNodeWithDetail(DebugSingleNodeVO vo) {
        String projectId = interfaceConfigService.getProjectIdOrNull(vo.getInterfaceId(), vo.getEditType());
        SingleNodeDebugResultVO response = debugService.debugNodeWithDetail(vo.getBaseNode(), projectId);
        logSingleNodeOperation(vo);
        return response;
    }

    /**
     * debug单节点
     * @param vo
     * @return
     */
    private Object debugSingleNode(DebugSingleNodeVO vo) {
        String projectId = interfaceConfigService.getProjectIdOrNull(vo.getInterfaceId(), vo.getEditType());
        Object response = debugService.debugNode(vo.getBaseNode(), projectId);
        logSingleNodeOperation(vo);
        return response != null ? response : GlobalResponse.successResult(null);
    }

    private void logSingleNodeOperation(DebugSingleNodeVO vo) {
        if (vo.getFunctionId() != null) {
            operationRecordService.add(OperationRecordType.FunctionSubType.DEBUG, vo.getFunctionId() + "");
        } else if (EditType.TASK.name().equalsIgnoreCase(vo.getEditType())) {
            operationRecordService.add(OperationRecordType.TaskSubType.DEBUG_SINGLE_NODE, vo.getInterfaceId() + "");
        } else {
            operationRecordService.add(OperationRecordType.InterfaceSubType.DEBUG_SINGLE_NODE, vo.getInterfaceId() + "");
        }
    }

    /**
     * @return 是否已导出
     */
    private boolean exportResultFileByExportType(DebugResult debugResult, HttpServletResponse response, String exportType) {
        if (Strings.isBlank(exportType)) {
            return false;
        }
        if (debugResult.skipReturnDebugResult) {
            return true;
        }
        ExportUtil.write(response, debugResult.getOutput(), exportType);
        return true;
    }


}
